package zdream.control.world;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import zdream.control.region.Gate;
import zdream.control.region.Region;
import zdream.control.region.Room;
import zdream.control.region.Terrains;
import zdream.utils.math.MathUtils;
import zdream.utils.math.Point2d;

import static zdream.control.region.Gate.*;

/**
 * <p>房间切换数据
 * </p>
 *
 * @author Zdream
 * @since 0.0.1
 * @date
 *   2019-05-17 (created)
 *   2022-05-30 (last modified)
 */
public class RoomTransmission {
	public final LevelWorld world;

	RoomTransmission(LevelWorld world) {
		this.world = world;
	}

	/* **********
	 * 绘画参数 *
	 ********** */
	private final List<MFoe> tmpFoes = new ArrayList<>();
	/**
	 * <p>暂存所有需要保留的角色, 也就是切换房间但不删除的 foe
	 * 设置它们的传送目标位置. (原来叫 entriesPos)
	 * <p>key 是 {@link Room} 切换时不删除的 {@link MFoe}
	 * <p>value 是 FoeTransmissionDesc, 含开始位置, 结束位置
	 * </p>
	 */
	public final Map<MFoe, FoeTransmissionDesc> foeDescMap = new HashMap<>();

	static class FoeTransmissionDesc {
		MFoe foe;
		Point2d from;
		Point2d to;
		boolean focus;
	}

	/**
	 * 镜头位置转换. 下面的数组记录了每个阶段结束 / 刚开始时镜头左下的位置 (不是中心点！)
	 * 初始   (index: 0)
	 * 阶段一 (index: 1)  源摆正
	 * 阶段二 (index: 2)  平移
	 * 阶段三 (index: 3)  目标房间 fix
	 *
	 * <p>确定移屏的相关参数. 移屏都是摄像机在动.
	 * <p>如果关卡设计合理的话, 是没有第一阶段的.
	 * </p>
	 */
	public final Point2d[] cameraPoses = new Point2d[] {
		new Point2d(), new Point2d(), new Point2d(), new Point2d()
	};

	public Gate gate = null;

	/* **********
	 * 时间状态 *
	 ********** */
	public static final byte PHASE_NOT_START_YET = -1;
	public static final byte PHASE_SRC_FIX = 0;
	public static final byte PHASE_TRANSMISSION = 1;
	public static final byte PHASE_DST_FIX = 2;
	/**
	 * 现在是第几个阶段
	 */
	public byte phase = PHASE_NOT_START_YET;

	/**
	 * 移动开始时, 记录的 worldTime
	 */
	public long startTime = 0;

	public boolean durationTransmission() {
		return phase >= 0;
	}

	/**
	 * <p>切换房间的判定
	 * <p>已经满足有大门的条件了, 还需要判定:
	 * <p>如果顺着重力及其它环境力的合力方向，碰到边缘就可以切房间；
	 * 如果逆着合力方向，就需要额外判定，比如需要在攀爬状态、踩在指定的角色上等
	 * </p>
	 */
	public boolean checkShift(MFoe foe, Gate g) {
		if (!touch(foe, g)) {
			return false;
		}

		if (g.left() || g.right()) {
			return true;
		}

		boolean gravityDown = foe.gravityDown && foe.gravityScale > 0
				|| !foe.gravityDown && foe.gravityScale < 0;

		if (gravityDown && g.down() || !gravityDown && g.up()) {
			// 顺着重力（合力）往下掉的
			return true;
		}

		// 这里判断比如需要在攀爬状态、踩在指定的角色上等. 满足条件可以传送
		return isClimbing(foe) || !foe.inAir;
	}

	private boolean isClimbing(MFoe foe) {
		return foe.confirm(MFoeConstants.CATEGORY_MOTION, MFoeConstants.TAG_CLIMBING);
	}

	private boolean touch(MFoe foe, Gate g) {
		Room room = world.currentRoom;
		if (g.left() || g.right()) {
			boolean edgeTouched = g.left() && foe.pos.left() <= 0
								|| g.right() && foe.pos.right() >= room.width;
			if (!edgeTouched) {
				return false;
			}

			float fTop = foe.pos.top();
			int top = MathUtils.floor(fTop);
			if (top == fTop) {
				top--;
			}
			if (top >= room.height) {
				return false;
			}
			int bottom = MathUtils.floor(foe.pos.bottom());
			if (bottom < 0) {
				return false;
			}

			int thisX = g.left() ? 0 : room.width - 1;
			int thatX = g.left() ? g.destRoom.width - 1 : 0;
			for (int y = bottom; y <= top; y++) {
				if (!Terrains.isEmpty(room.terrains[thisX][y])) {
					return false;
				}
				int thatY = y + room.offsetY - g.destRoom.offsetY;
				if (!Terrains.isEmpty(g.destRoom.terrains[thatX][thatY])) {
					return false;
				}
			}
		} else { // up & down
			boolean edgeTouched = g.down() && foe.pos.bottom() <= 0
								|| g.up() && foe.pos.top() >= room.height;
			if (!edgeTouched) {
				return false;
			}

			float fRight = foe.pos.right();
			int right = MathUtils.floor(fRight);
			if (right == fRight) {
				right--;
			}
			if (right >= room.width) {
				return false;
			}
			int left = MathUtils.floor(foe.pos.left());
			if (left < 0) {
				return false;
			}

			int thisY = g.down() ? 0 : room.height - 1;
			int thatY = g.down() ? g.destRoom.height - 1 : 0;
			for (int x = left; x <= right; x++) {
				if (!Terrains.isEmpty(room.terrains[x][thisY])) {
					return false;
				}
				int thatX = x + room.offsetX - g.destRoom.offsetX;
				if (!Terrains.isEmpty(g.destRoom.terrains[thatX][thatY])) {
					return false;
				}
			}
		}
		return true;
	}

	public void startTransmission(Gate gate, List<MFoe> retainFoes) {
		this.gate = gate;
		// TODO 其它房间转换时保留的 foes

		Room srcRoom = gate.srcRoom, destRoom = gate.destRoom;

		List<MFoe> allFoes = world.foes;
		tmpFoes.addAll(allFoes);
		for (MFoe foe : tmpFoes) {
			if (!retainFoes.contains(foe)) {
				foe.destroy();
				continue;
			}

			FoeTransmissionDesc desc = new FoeTransmissionDesc();
			desc.foe = foe;
			desc.from = new Point2d(foe.anchorX, foe.anchorY);
			desc.to = new Point2d(
					foe.anchorX + srcRoom.offsetX - destRoom.offsetX,
					foe.anchorY + srcRoom.offsetY - destRoom.offsetY);
			desc.focus = world.camera.focus.contains(foe);
			foeDescMap.put(foe, desc);
			foe.active = false;
		}
		world.foes.clear();
		world.foes.addAll(retainFoes);
		world.camera.focus.clear();
		tmpFoes.clear();

		this.phase = PHASE_SRC_FIX;
		LevelCamera camera = world.camera;

		// TODO 如果 destRoom 来自其它的区域, 还需要修改数据. 当前不支持跨 tmx 切换
		Point2d curPos = cameraPoses[0];
		curPos.set(camera.view.x, camera.view.y);

		float roomOffX = srcRoom.offsetX;
		float roomOffY = srcRoom.offsetY;

		switch (gate.direction) {
		case DIRECTION_LEFT:
		case DIRECTION_RIGHT: {
			// y -> x -> y
			// 定 y 轴
			cameraPoses[3].y = MathUtils.clamp(curPos.y,
					destRoom.offsetY - roomOffY,
					destRoom.offsetY + destRoom.height - roomOffY);
			cameraPoses[1].y = MathUtils.clamp(cameraPoses[3].y,
					srcRoom.offsetY - roomOffY,
					srcRoom.offsetY + srcRoom.height - roomOffY);
			cameraPoses[2].y = cameraPoses[1].y;
			// 定 x 轴
			cameraPoses[1].x = cameraPoses[0].x;
		} break;

		case DIRECTION_BOTTOM:
		case DIRECTION_TOP: {
			// x -> y -> x
			// 定 x 轴
			cameraPoses[3].x = MathUtils.clamp(curPos.x,
					destRoom.offsetX - roomOffX,
					destRoom.offsetX + destRoom.width - roomOffX);
			cameraPoses[1].x = MathUtils.clamp(cameraPoses[3].x,
					srcRoom.offsetX - roomOffX,
					srcRoom.offsetX + srcRoom.width - roomOffX);
			cameraPoses[2].x = cameraPoses[1].x;
			// 定 y 轴
			cameraPoses[1].y = cameraPoses[0].y;
		} break;

		default:
			throw new IllegalArgumentException("未知的 direction: " + gate.direction);
		}

		switch (gate.direction) {
		case DIRECTION_LEFT: {
			cameraPoses[3].x = cameraPoses[2].x = cameraPoses[1].x - Region.BLOCK_WIDTH;
			for (MFoe foe : retainFoes) {// 设置为右边距离房间边界 0.5f
				foeDescMap.get(foe).to.x = -0.5f - foe.boxWidth - foe.boxX;
			}
		} break;
		case DIRECTION_RIGHT: {
			cameraPoses[3].x = cameraPoses[2].x = cameraPoses[1].x + Region.BLOCK_WIDTH;
			for (MFoe foe : retainFoes) {// 设置为左边距离房间边界 0.5f
				foeDescMap.get(foe).to.x = srcRoom.width + 0.5f - foe.boxX;
			}
		} break;
		case DIRECTION_BOTTOM: {
			cameraPoses[3].y = cameraPoses[2].y = cameraPoses[1].y - Region.BLOCK_HEIGHT;
			for (MFoe foe : retainFoes) {// 设置为上边距离房间边界 0.2f
				foeDescMap.get(foe).to.y = -0.2f - foe.boxHeight - foe.boxY;
			}
		} break;
		case DIRECTION_TOP: {
			cameraPoses[3].y = cameraPoses[2].y = cameraPoses[1].y + Region.BLOCK_HEIGHT;
			for (MFoe foe : retainFoes) {// 设置为下边距离房间边界 0.2f
				foeDescMap.get(foe).to.y = srcRoom.height + 0.2f - foe.boxY;
			}
		} break;
		}

		startTime = world.ticker.worldTime;
		world.inRoomTransmission = true;
		world.ticker.foePaused = true;
	}

	private void finishTransmission(Ticker ticker) {
		phase = PHASE_NOT_START_YET;
		world.inRoomTransmission = false;
		world.ticker.foePaused = false;
		world.setCurrentRoom(gate.destRoom);

		int offsetX = gate.srcRoom.offsetX - gate.destRoom.offsetX;
		int offsetY = gate.srcRoom.offsetY - gate.destRoom.offsetY;

		// levelCamera
		world.camera.setLeftBottomPointAt(
				cameraPoses[3].x + offsetX, cameraPoses[3].y + offsetY);

		// retainFoes
		for (MFoe foe : foeDescMap.keySet()) {
			FoeTransmissionDesc desc = foeDescMap.get(foe);
			foe.setAnchor(desc.to.x + offsetX, desc.to.y + offsetY);
			if (desc.focus) {
				world.camera.focus.add(foe);
			}
			foe.active = true;
		}

		// clean
		foeDescMap.clear();

		gate = null;
	}

	public void tickTransmission(Ticker ticker) {
		switch (phase) {
		case PHASE_SRC_FIX:
			shiftSrcFix(ticker);
			break;
		case PHASE_TRANSMISSION:
			shiftTransmission(ticker);
			break;
		case PHASE_DST_FIX:
			shiftDstFix(ticker);
			break;
		default:
			throw new IllegalStateException("移屏阶段数据出错: " + phase);
		}
	}

	private static final float TRANSMISSION_SPEED_VX = 25;
	private static final float TRANSMISSION_SPEED_VY = 15;

	private void shiftSrcFix(Ticker ticker) {
		float process = shift(ticker, cameraPoses[0], cameraPoses[1]);
		if (process == 1) {
			phase = PHASE_TRANSMISSION;
		}
	}

	private void shiftTransmission(Ticker ticker) {
		float process = shift(ticker, cameraPoses[1], cameraPoses[2]);
		if (process == 1) {
			phase = PHASE_DST_FIX;
		}

		for (MFoe foe : foeDescMap.keySet()) {
			final FoeTransmissionDesc desc = foeDescMap.get(foe);
			Point2d startPos = desc.from;
			Point2d targetPos = desc.to;
			foe.setAnchor(
					startPos.x + (targetPos.x - startPos.x) * process,
					startPos.y + (targetPos.y - startPos.y) * process);
		}
	}

	private void shiftDstFix(Ticker ticker) {
		float process = shift(ticker, cameraPoses[2], cameraPoses[3]);
		if (process != 1) {
			return;
		}
		finishTransmission(ticker);
	}

	/**
	 * @return 返回 0~1 表示进度, 0 表示刚开始, 1 表示结束
	 */
	private float shift(Ticker ticker, Point2d start, Point2d end) {
		if (start.x != end.x) {
			return shiftX(ticker, start, end, world.camera);
		} else {
			return shiftY(ticker, start, end, world.camera);
		}
	}

	private float shiftX(Ticker ticker, Point2d start, Point2d end, LevelCamera camera) {
		float deltaX = ticker.duration * TRANSMISSION_SPEED_VX;
		float remain = Math.abs(camera.view.x - end.x);
		if (remain < deltaX) {
			camera.setLeftBottomPointXNotRestrict(end.x);
			return 1;
		}

		if (end.x > camera.view.x) {
			camera.setLeftBottomPointXNotRestrict(camera.view.x + deltaX);
		} else {
			camera.setLeftBottomPointXNotRestrict(camera.view.x - deltaX);
		}
		return (camera.view.x - start.x) / (end.x - start.x);
	}

	private float shiftY(Ticker ticker, Point2d start, Point2d end, LevelCamera camera) {
		float deltaY = ticker.duration * TRANSMISSION_SPEED_VY;
		float remain = Math.abs(camera.view.y - end.y);
		if (remain < deltaY) {
			camera.setLeftBottomPointYNotRestrict(end.y);
			return 1;
		}

		if (end.y > camera.view.y) {
			camera.setLeftBottomPointYNotRestrict(camera.view.y + deltaY);
		} else {
			camera.setLeftBottomPointYNotRestrict(camera.view.y - deltaY);
		}
		return (camera.view.y - start.y) / (end.y - start.y);
	}
}
